﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.AspNet;
using Microsoft.Web.WebPages.OAuth;
using WebMatrix.WebData;
using MvcApplication1.Filters;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
	 [Authorize]
	 [InitializeSimpleMembership]
	 public class AccountController : Controller
	 {
		  //
		  // GET: /Account/Login

		  [AllowAnonymous]
		  public ActionResult Login( string returnUrl ) {
				ViewBag.ReturnUrl = returnUrl;
				return View();
		  }

		  //
		  // POST: /Account/Login

		  [HttpPost]
		  [AllowAnonymous]
		  [ValidateAntiForgeryToken]
		  public ActionResult Login( LoginModel model , string returnUrl ) {
				if( ModelState.IsValid && WebSecurity.Login( model.UserName , model.Password , persistCookie: model.RememberMe ) ) {
					 return RedirectToLocal( returnUrl );
				}

				// If we got this far, something failed, redisplay form
				ModelState.AddModelError( "" , "The user name or password provided is incorrect." );
				return View( model );
		  }

		  //
		  // POST: /Account/LogOff

		  [HttpPost]
		  [ValidateAntiForgeryToken]
		  public ActionResult LogOff() {
				WebSecurity.Logout();

				return RedirectToAction( "Index" , "Home" );
		  }

		  //
		  // GET: /Account/Register

		  [AllowAnonymous]
		  public ActionResult Register() {
				return View();
		  }

		  //
		  // POST: /Account/Register

		  [HttpPost]
		  [AllowAnonymous]
		  [ValidateAntiForgeryToken]
		  public ActionResult Register( RegisterModel model ) {
				if( ModelState.IsValid ) {
					 // Attempt to register the user
					 try {
						  WebSecurity.CreateUserAndAccount( model.UserName , model.Password );
						  WebSecurity.Login( model.UserName , model.Password );
						  return RedirectToAction( "Index" , "Home" );
					 }
					 catch( MembershipCreateUserException e ) {
						  ModelState.AddModelError( "" , ErrorCodeToString( e.StatusCode ) );
					 }
				}

				// If we got this far, something failed, redisplay form
				return View( model );
		  }

		  //
		  // POST: /Account/Disassociate

		  [HttpPost]
		  [ValidateAntiForgeryToken]
		  public ActionResult Disassociate( string provider , string providerUserId ) {
				string ownerAccount = OAuthWebSecurity.GetUserName( provider , providerUserId );
				ManageMessageId? message = null;

				// Only disassociate the account if the currently logged in user is the owner
				if( ownerAccount == User.Identity.Name ) {
					 // Use a transaction to prevent the user from deleting their last login credential
					 using( var scope = new TransactionScope( TransactionScopeOption.Required , new TransactionOptions { IsolationLevel = IsolationLevel.Serializable } ) ) {
						  bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
						  if( hasLocalAccount || OAuthWebSecurity.GetAccountsFromUserName( User.Identity.Name ).Count > 1 ) {
								OAuthWebSecurity.DeleteAccount( provider , providerUserId );
								scope.Complete();
								message = ManageMessageId.RemoveLoginSuccess;
						  }
					 }
				}

				return RedirectToAction( "Manage" , new { Message = message } );
		  }

		  //
		  // GET: /Account/Manage

		  public ActionResult Manage( ManageMessageId? message ) {
				ViewBag.StatusMessage =
					 message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
					 : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
					 : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
					 : "";
				ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
				ViewBag.ReturnUrl = Url.Action( "Manage" );
				return View();
		  }

		  //
		  // POST: /Account/Manage

		  [HttpPost]
		  [ValidateAntiForgeryToken]
		  public ActionResult Manage( LocalPasswordModel model ) {
				bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
				ViewBag.HasLocalPassword = hasLocalAccount;
				ViewBag.ReturnUrl = Url.Action( "Manage" );
				if( hasLocalAccount ) {
					 if( ModelState.IsValid ) {
						  // ChangePassword will throw an exception rather than return false in certain failure scenarios.
						  bool changePasswordSucceeded;
						  try {
								changePasswordSucceeded = WebSecurity.ChangePassword( User.Identity.Name , model.OldPassword , model.NewPassword );
						  }
						  catch( Exception ) {
								changePasswordSucceeded = false;
						  }

						  if( changePasswordSucceeded ) {
								return RedirectToAction( "Manage" , new { Message = ManageMessageId.ChangePasswordSuccess } );
						  } else {
								ModelState.AddModelError( "" , "The current password is incorrect or the new password is invalid." );
						  }
					 }
				} else {
					 // User does not have a local password so remove any validation errors caused by a missing
					 // OldPassword field
					 ModelState state = ModelState["OldPassword"];
					 if( state != null ) {
						  state.Errors.Clear();
					 }

					 if( ModelState.IsValid ) {
						  try {
								WebSecurity.CreateAccount( User.Identity.Name , model.NewPassword );
								return RedirectToAction( "Manage" , new { Message = ManageMessageId.SetPasswordSuccess } );
						  }
						  catch( Exception e ) {
								ModelState.AddModelError( "" , e );
						  }
					 }
				}

				// If we got this far, something failed, redisplay form
				return View( model );
		  }

		  //
		  // POST: /Account/ExternalLogin

		  [HttpPost]
		  [AllowAnonymous]
		  [ValidateAntiForgeryToken]
		  public ActionResult ExternalLogin( string provider , string returnUrl ) {
				return new ExternalLoginResult( provider , Url.Action( "ExternalLoginCallback" , new { ReturnUrl = returnUrl } ) );
		  }

		  //
		  // GET: /Account/ExternalLoginCallback

		  [AllowAnonymous]
		  public ActionResult ExternalLoginCallback( string returnUrl ) {
				AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication( Url.Action( "ExternalLoginCallback" , new { ReturnUrl = returnUrl } ) );
				if( !result.IsSuccessful ) {
					 return RedirectToAction( "ExternalLoginFailure" );
				}

				if( OAuthWebSecurity.Login( result.Provider , result.ProviderUserId , createPersistentCookie: false ) ) {
					 return RedirectToLocal( returnUrl );
				}

				if( User.Identity.IsAuthenticated ) {
					 // If the current user is logged in add the new account
					 OAuthWebSecurity.CreateOrUpdateAccount( result.Provider , result.ProviderUserId , User.Identity.Name );
					 return RedirectToLocal( returnUrl );
				} else {
					 // User is new, ask for their desired membership name
					 string loginData = OAuthWebSecurity.SerializeProviderUserId( result.Provider , result.ProviderUserId );
					 ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData( result.Provider ).DisplayName;
					 ViewBag.ReturnUrl = returnUrl;
					 return View( "ExternalLoginConfirmation" , new RegisterExternalLoginModel { UserName = result.UserName , ExternalLoginData = loginData } );
				}
		  }

		  //
		  // POST: /Account/ExternalLoginConfirmation

		  [HttpPost]
		  [AllowAnonymous]
		  [ValidateAntiForgeryToken]
		  public ActionResult ExternalLoginConfirmation( RegisterExternalLoginModel model , string returnUrl ) {
				string provider = null;
				string providerUserId = null;

				if( User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId( model.ExternalLoginData , out provider , out providerUserId ) ) {
					 return RedirectToAction( "Manage" );
				}

				if( ModelState.IsValid ) {
					 // Insert a new user into the database
					 using( UsersContext db = new UsersContext() ) {
						  UserProfile user = db.UserProfiles.FirstOrDefault( u => u.UserName.ToLower() == model.UserName.ToLower() );
						  // Check if user already exists
						  if( user == null ) {
								// Insert name into the profile table
								db.UserProfiles.Add( new UserProfile { UserName = model.UserName } );
								db.SaveChanges();

								OAuthWebSecurity.CreateOrUpdateAccount( provider , providerUserId , model.UserName );
								OAuthWebSecurity.Login( provider , providerUserId , createPersistentCookie: false );

								return RedirectToLocal( returnUrl );
						  } else {
								ModelState.AddModelError( "UserName" , "User name already exists. Please enter a different user name." );
						  }
					 }
				}

				ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData( provider ).DisplayName;
				ViewBag.ReturnUrl = returnUrl;
				return View( model );
		  }

		  //
		  // GET: /Account/ExternalLoginFailure

		  [AllowAnonymous]
		  public ActionResult ExternalLoginFailure() {
				return View();
		  }

		  [AllowAnonymous]
		  [ChildActionOnly]
		  public ActionResult ExternalLoginsList( string returnUrl ) {
				ViewBag.ReturnUrl = returnUrl;
				return PartialView( "_ExternalLoginsListPartial" , OAuthWebSecurity.RegisteredClientData );
		  }

		  [ChildActionOnly]
		  public ActionResult RemoveExternalLogins() {
				ICollection<OAuthAccount> accounts = OAuthWebSecurity.GetAccountsFromUserName( User.Identity.Name );
				List<ExternalLogin> externalLogins = new List<ExternalLogin>();
				foreach( OAuthAccount account in accounts ) {
					 AuthenticationClientData clientData = OAuthWebSecurity.GetOAuthClientData( account.Provider );

					 externalLogins.Add( new ExternalLogin
					 {
						  Provider = account.Provider ,
						  ProviderDisplayName = clientData.DisplayName ,
						  ProviderUserId = account.ProviderUserId ,
					 } );
				}

				ViewBag.ShowRemoveButton = externalLogins.Count > 1 || OAuthWebSecurity.HasLocalAccount( WebSecurity.GetUserId( User.Identity.Name ) );
				return PartialView( "_RemoveExternalLoginsPartial" , externalLogins );
		  }

		  #region Helpers
		  private ActionResult RedirectToLocal( string returnUrl ) {
				if( Url.IsLocalUrl( returnUrl ) ) {
					 return Redirect( returnUrl );
				} else {
					 return RedirectToAction( "Index" , "Home" );
				}
		  }

		  public enum ManageMessageId
		  {
				ChangePasswordSuccess ,
				SetPasswordSuccess ,
				RemoveLoginSuccess ,
		  }

		  internal class ExternalLoginResult : ActionResult
		  {
				public ExternalLoginResult( string provider , string returnUrl ) {
					 Provider = provider;
					 ReturnUrl = returnUrl;
				}

				public string Provider { get; private set; }
				public string ReturnUrl { get; private set; }

				public override void ExecuteResult( ControllerContext context ) {
					 OAuthWebSecurity.RequestAuthentication( Provider , ReturnUrl );
				}
		  }

		  private static string ErrorCodeToString( MembershipCreateStatus createStatus ) {
				// See http://go.microsoft.com/fwlink/?LinkID=177550 for
				// a full list of status codes.
				switch( createStatus ) {
					 case MembershipCreateStatus.DuplicateUserName:
						  return "User name already exists. Please enter a different user name.";

					 case MembershipCreateStatus.DuplicateEmail:
						  return "A user name for that e-mail address already exists. Please enter a different e-mail address.";

					 case MembershipCreateStatus.InvalidPassword:
						  return "The password provided is invalid. Please enter a valid password value.";

					 case MembershipCreateStatus.InvalidEmail:
						  return "The e-mail address provided is invalid. Please check the value and try again.";

					 case MembershipCreateStatus.InvalidAnswer:
						  return "The password retrieval answer provided is invalid. Please check the value and try again.";

					 case MembershipCreateStatus.InvalidQuestion:
						  return "The password retrieval question provided is invalid. Please check the value and try again.";

					 case MembershipCreateStatus.InvalidUserName:
						  return "The user name provided is invalid. Please check the value and try again.";

					 case MembershipCreateStatus.ProviderError:
						  return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

					 case MembershipCreateStatus.UserRejected:
						  return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

					 default:
						  return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
				}
		  }
		  #endregion
	 }
}
